package com.google.android.apps.authenticator;

import java.util.ArrayList;

import android.app.AlarmManager;
import android.app.PendingIntent;
import android.appwidget.AppWidgetManager;
import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.util.Log;
import android.widget.RemoteViews;

import com.google.android.apps.authenticator.testability.DependencyInjector;
import com.google.android.apps.authenticator2.R;

public class AuthenticatorWidgetProvider extends AppWidgetProvider {
	
	public static final String AUTHENTICATOR_UPDATE_PIN = "com.google.android.apps.authenticator.AUTHENTICATOR_UPDATE_PIN";
	public static final String AUTHENTICATOR_UPDATE_TIME_REMAINING = "com.google.android.apps.authenticator.AUTHENTICATOR_UPDATE_TIME_REMAINING";
	
	private AccountDb mAccountDb;
	private OtpSource mOtpProvider;
	private Context mContext;

	@Override
	public void onEnabled(Context ctx) {		
		super.onEnabled(ctx);
		
		
		if(this.mContext == null) {
			this.mContext = ctx;
		}
		
		ComponentName cname = new ComponentName(ctx, AuthenticatorWidgetProvider.class);
		AppWidgetManager manager = AppWidgetManager.getInstance(ctx);
		int[] widgetIDs = manager.getAppWidgetIds(cname);
		this.updateWidgetDispayIfSetup(ctx, manager, widgetIDs);		
	}
	
	@Override
	public void onDeleted(Context context, int[] appWidgetIds) {		
		super.onDeleted(context, appWidgetIds);
		
		if(appWidgetIds == null || appWidgetIds.length == 0) {
			return;
		}
		
		for(int widgetID : appWidgetIds) {
			SharedPreferences prefs = this.getSharedPrefsForWidgetID(widgetID, context);
			String account = this.getAccountForWidgetID(widgetID, context, prefs);
			
			if(prefs == null) {
				return;
			}
			
			SharedPreferences.Editor edit = prefs.edit();
			edit.clear();
			edit.commit();
			
			if(account == null) {
				return;
			}
			
			PendingIntent intent = this.createUpdatePinIntent(context, widgetID, account);
			PendingIntent barIntent = this.createUpdateBarIntent(context, widgetID, account);
			Log.v("AuthenticatorWidgetProvider.onDeleted", "Canceling alarm for widget " + widgetID + " and account " + account);
			AlarmManager alarm = (AlarmManager)context.getSystemService(Context.ALARM_SERVICE);
			alarm.cancel(intent);
			alarm.cancel(barIntent);
		}
		
		
		
	}
	
	@Override
	public void onReceive(Context context, Intent intent){
		super.onReceive(context, intent);
		
		if(intent.getAction() == AUTHENTICATOR_UPDATE_PIN) {
			try {
				updateWidgetPins(context, intent);
			} catch (OtpSourceException e) {
				// TODO Auto-generated catch block
				Log.e("AuthenticatorWidgetProvider.onReceive", "exception getting code", e);
				e.printStackTrace();
			}
		}
		
		int widgetID = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
		String account = intent.getStringExtra(ConfigureAuthenticatorWidgetActivity.WIDGET_SELECTED_ACCOUNT_KEY);
		AppWidgetManager appwidgetmanager = AppWidgetManager.getInstance(context);
		
		if(intent.getAction() == AUTHENTICATOR_UPDATE_TIME_REMAINING) {
			updateTimeRemaining(context, appwidgetmanager, widgetID, account);
		}
		
		if(intent.getAction() == ConfigureAuthenticatorWidgetActivity.WIDGET_CONFIG_COMPLETE) {
			Log.v("AuthenticatorWidgetProvider.onReceive", "Widget id " + widgetID + " Accoung " + account);			
			updateWidgetDisplay(context, appwidgetmanager,  widgetID, account);
		}
		
	}
	
	protected void updateTimeRemaining(Context ctx, AppWidgetManager appWidgetManager, int widgetID, String account) {
		
		long timeRemaining = this.getRemainingTimeForCurrentCode(ctx);		
		RemoteViews view = new RemoteViews(ctx.getPackageName(), R.layout.widget_view);
		view.setProgressBar(R.id.pin_progress, 30000, (int)timeRemaining, false);		
		appWidgetManager.updateAppWidget(widgetID, view);
	}
	
	protected void updateWidgetPins(Context ctx, Intent intent) throws OtpSourceException {
		this.getProvidersIfNotSet(ctx);
		
		final int widgetID = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
		String account = intent.getStringExtra(ConfigureAuthenticatorWidgetActivity.WIDGET_SELECTED_ACCOUNT_KEY);
		
		if(widgetID == -1) {
			Log.e("AuthenticatorWidgetProvider.updateWidgetPins", "invalid widgetID");
			return;
		}
		
		if(account == null) {
			Log.e("AuthenticatorWidgetProvider.updateWidgetPins", "account is null");
			return;
		}
				
		AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(ctx);	
		String code = mOtpProvider.getNextCode(account);
		
		RemoteViews  view = new RemoteViews(ctx.getPackageName(), R.layout.widget_view);
		view.setTextViewText(R.id.pin_value, code);
		appWidgetManager.updateAppWidget(widgetID, view);
	}
	
	protected void getProvidersIfNotSet(Context ctx) {
		
		if(DependencyInjector.getContext() == null) {
			Log.d("AuthenticatorWidgetProvider.getProvidersIfNotSet", "setting DependencyInjector's Context");
			DependencyInjector.configureForProductionIfNotConfigured(ctx);
		}
		
		mAccountDb = DependencyInjector.getAccountDb();
		mOtpProvider = DependencyInjector.getOtpProvider();
	}
	
	protected SharedPreferences getSharedPrefsForWidgetID(int widgetID, Context ctx) {
		SharedPreferences prefs = ctx.getSharedPreferences(ConfigureAuthenticatorWidgetActivity.WIDGET_SHARED_PREFERENCE_KEY_PREFIX + widgetID, Context.MODE_PRIVATE);
		
		if(prefs == null || prefs.getAll().isEmpty()) {
			Log.e("AuthenticatorWidgetProvider.getSharedPrefsForWidgetID", "Preferences for widget " + widgetID + " is null or empty");
		}
		
		return prefs;
	}
	
	protected String getAccountForWidgetID(int widgetID,  Context ctx) {
		return getAccountForWidgetID(widgetID, ctx, getSharedPrefsForWidgetID(widgetID, ctx));
	}
	
	protected String getAccountForWidgetID(int widgetID,  Context ctx, SharedPreferences prefs) {
		return prefs.getString(ConfigureAuthenticatorWidgetActivity.WIDGET_SELECTED_ACCOUNT_KEY, null);
	}
	
	@Override
	public void onUpdate(Context ctx, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
		super.onUpdate(ctx, appWidgetManager, appWidgetIds);	
		updateWidgetDispayIfSetup(ctx, appWidgetManager, appWidgetIds);
		
	}
	
	
	protected void updateWidgetDispayIfSetup(Context ctx, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
		for(int widgetID : appWidgetIds) {
			String account = this.getAccountForWidgetID(widgetID, ctx);
			if(account != null) {
				this.updateWidgetDisplay(ctx, appWidgetManager,  widgetID, account);
			}
		}
	}
	
	protected void updateWidgetDisplay(Context ctx, AppWidgetManager appWidgetManager, int appWidgetId, String account) {
		
		if(appWidgetId == -1) {
			Log.e("AuthenticatorWidgetProvider.updateWidgetDisplay", "appWidgetIds is invalid");
			return;
		}
				
		if(account == null) {
			Log.e("AuthenticatorWidgetProvider.updateWidgetDisplay", "account is null");
		}
		
		getProvidersIfNotSet(ctx);
		
		ArrayList<String> names = new ArrayList<String>();		
		mAccountDb.getNames(names);			
		
		if(!names.contains(account)) {
			Log.e("AuthenticatorWidgetProvider.updateWidgetDisplay", "account " + account + " does not exist in the accounts list");
			return;
		}
		
		RemoteViews  view = new RemoteViews(ctx.getPackageName(), R.layout.widget_view);
		
		view.setTextViewText(R.id.current_user, account);
		String code = "";
		try {
			code = mOtpProvider.getNextCode(account);
		} catch (OtpSourceException e) {
			// TODO Auto-generated catch block
			Log.e("AuthenticatorWidgetProvider.updateWidgetDisplay", "exception getting code for name " + account, e);
			e.printStackTrace();
			
		}
		
		view.setTextViewText(R.id.pin_value, code);
		appWidgetManager.updateAppWidget(appWidgetId, view);
		
		
		AlarmManager alarm = (AlarmManager)ctx.getSystemService(Context.ALARM_SERVICE);
		PendingIntent intent = createUpdatePinIntent(ctx, appWidgetId, account);
		
		long nextTime = this.getNextCodeStartTime(ctx);
		alarm.setRepeating(AlarmManager.RTC, nextTime, Utilities.secondsToMillis(this.mOtpProvider.getTotpCounter().getTimeStep()), intent);
		
		
		PendingIntent barIntent = this.createUpdateBarIntent(ctx, appWidgetId, account);
		alarm.setRepeating(AlarmManager.RTC, nextTime, 5000, barIntent);
				
	}
	
	protected PendingIntent createUpdatePinIntent(Context ctx, int widgetID, String account) {
		Intent current = new Intent(ctx, AuthenticatorWidgetProvider.class);
		current.setAction(AUTHENTICATOR_UPDATE_PIN);
		current.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetID);
		current.putExtra(ConfigureAuthenticatorWidgetActivity.WIDGET_SELECTED_ACCOUNT_KEY, account);
		
		PendingIntent intent = PendingIntent.getBroadcast(ctx, 0, current, PendingIntent.FLAG_CANCEL_CURRENT);
		return intent;
		
	}
	
	protected PendingIntent createUpdateBarIntent(Context ctx, int widgetID, String account) {
		Intent current = new Intent(ctx, AuthenticatorWidgetProvider.class);
		current.setAction(AUTHENTICATOR_UPDATE_TIME_REMAINING);
		current.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetID);
		current.putExtra(ConfigureAuthenticatorWidgetActivity.WIDGET_SELECTED_ACCOUNT_KEY, account);
		
		PendingIntent intent = PendingIntent.getBroadcast(ctx, 0, current, PendingIntent.FLAG_CANCEL_CURRENT);
		return intent;
		
	}
	
	protected void saveWidgetSettings(Context ctx, int widgetID, String account) {
		SharedPreferences.Editor prefs = ctx.getSharedPreferences(ConfigureAuthenticatorWidgetActivity.WIDGET_SHARED_PREFERENCE_KEY_PREFIX + widgetID, Context.MODE_PRIVATE).edit();						
		prefs.putString(ConfigureAuthenticatorWidgetActivity.WIDGET_SELECTED_ACCOUNT_KEY, account);
		if(!prefs.commit()) {
			Log.e("AuthenticatorWidgetProvider.onCreate.selectAccountButton.onClick", "Commit preferences failed");
		}
	}
	
	protected long getRemainingTimeForCurrentCode(Context ctx) {
		
		this.getProvidersIfNotSet(ctx);
		
		TotpClock timer = mOtpProvider.getTotpClock();
		long time = timer.currentTimeMillis();
		long nextStartTime = this.getNextCodeStartTime(ctx, time);
		return nextStartTime - time;
	}
	
	protected long getNextCodeStartTime(Context ctx) {
		
		this.getProvidersIfNotSet(ctx);
		
		return this.getNextCodeStartTime(ctx, mOtpProvider.getTotpClock().currentTimeMillis());
	}
	
	protected long getNextCodeStartTime(Context ctx, long time) {
		
		this.getProvidersIfNotSet(ctx);
		
		TotpCounter counter = mOtpProvider.getTotpCounter();
		
		long val = counter.getValueAtTime(Utilities.millisToSeconds(time));
		long nextVal = val+1;
		return Utilities.secondsToMillis(counter.getValueStartTime(nextVal));
	
	}
}